/**
 * @file msg.h
 * Header of the <b>Msg<b/> class.
 */

#ifndef _MSG_H
#define _MSG_H

#include <stdio.h>
#include "addr.h"
#include "node.h"

/* Constants defined in the RFC P2P3TC, Section 2.3 */
#define P2P_VERSION              1

#define P2P_MSG_UNDEFINED        0
#define P2P_MSG_JOIN_REQ         4
#define P2P_MSG_JOIN_ACK         5
#define P2P_MSG_LINK_UPDATE      6
#define P2P_MSG_SEARCH           8
#define P2P_MSG_REPLY            9
#define P2P_MSG_NEIGHBORS_REQ   10
#define P2P_MSG_NEIGHBORS_LIST  11 
#define P2P_MSG_GET             12
#define P2P_MSG_DATA            13

#define P2P_MSG_TTL_ONE 1
#define P2P_MSG_TTL_DFL 8  // default TTL
#define P2P_MSG_TTL_MAX 16 // max TTL

#define P2P_HDR_BITFIELD_SIZE 4 // bytes
#define P2P_HDR_SIZE (P2P_HDR_BITFIELD_SIZE + 2*P2P_ADDR_SIZE)

#define P2P_MSG_MAX_DATA_SIZE 65535 // 16 bits for DATA LENGTH field => 2^16-1

/* Constants identifying left and right neighbours, Section 2.4 */
#define RIGHT 0x0000FFFF
#define LEFT 0xFFFF0000

typedef struct _Msg Msg;

Msg *msg_new();
void msg_delete(Msg **msg);
void msg_clear(Msg *msg);
void msg_cpy(Msg *dst, const Msg *src);
Msg *msg_dup(const Msg *msg);
void msg_set_hdr(Msg *msg, unsigned char version, unsigned char type, unsigned char ttl, const Addr *src, const Addr *dst);
void msg_set_join_ack(Msg *msg, const Addr *left, const Addr *right);
void msg_set_link_up(Msg *msg, const Addr *neighbour, uint32_t side);
void msg_set_search(Msg *msg, const Addr *bcast_init, uint32_t req_id, const char *file_name);
void msg_set_reply(Msg *msg, uint32_t req_id, uint32_t file_size);
void msg_set_neighbours_req();
void msg_set_neighbours_list(Msg *msg, Addr *neighbour1, Addr *neighbour2, char *node_name);
void msg_set_get(Msg *msg, uint32_t begin, uint32_t end, char *file_name);
void msg_set_data(Msg *msg, uint8_t status, uint32_t value);

int msg_hdr_read(const Node *node, Msg *msg, const char *buf);
void msg_hdr_write(const Msg *msg, char *buf);

int msg_data_read(const Node *node, Msg *msg, const char *buf);
int msg_data_write(const Msg *msg, char *buf);

int msg_read_tcp(Node *node, Msg *msg, int sock);
int msg_read_udp(Node *node, Msg *msg, int sock);
int msg_write_tcp(Node *node, const Msg *msg, int sock);
int msg_write_udp(Node *node, const Msg *msg, int sock);
int msg_send_tcp(Node *node, const Msg *msg);
int msg_send_udp(Node *node, const Msg *msg);
void msg_rebroadcast(Node *node, const Msg *msg);

void msg_dump(Node *node, const Msg *msg);

struct _Msg {
	/* Header */
	unsigned char version; // Version     4
	unsigned char type;    // CmdNum      4
	unsigned char ttl;     // TTL         8
	unsigned short length; // Data Length 16
	Addr src;              // Src Address 64
	Addr dst;              // Dst Address 64
	// Total header size: 20 bytes
	/* Data */
	union {
		/* P2P_JOIN_REQ */
		struct {
		} join_req;

		/* P2P_JOIN_ACK */
		struct {
			Addr left;
			Addr right;
		} join_ack;

		/* P2P_LINK_UPDATE */
		struct {
			Addr neighbour;
			uint32_t side;
		} link_up;

		/* P2P_SEARCH */
		struct {
			Addr bcast_init;
			uint32_t req_id;
			char file_name[P2P_MSG_MAX_DATA_SIZE-12+1];
		} search;

		/* P2P_REPLY */
		struct {
			uint32_t req_id;
			uint32_t file_size;
		} reply;

		/* P2P_NEIGHBORS_REQ */
		struct {
			Addr bcast_init;
		} neighbours_req;

		/* P2P_NEIGHBORS_LIST */
		struct {
			uint8_t N;
			// 3B not used
			Addr neighbour1;
			Addr neighbour2;
			char node_name[P2P_MSG_MAX_DATA_SIZE-4+1]; // enough space in case N = 0
		} neighbours_list;

		/* P2P_GET */
		struct {
			uint32_t begin;
			uint32_t end;
			char file_name[P2P_MSG_MAX_DATA_SIZE-8+1];
		} get;

		/* P2P_DATA */
		struct {
			uint8_t status;
			// 3B not used
			uint32_t value;
			char data[P2P_MSG_MAX_DATA_SIZE-8];
		} data;
	} d; // d as in "data", make calls shorter
};
#endif /* _MSG_H */
